import objectAssign from "common/js/utils/merge";
import { markNodeData, NODE_KEY } from "./util";
import { arrayFindIndex } from "common/js/utils/util";
export const getChildState = (node) => {
	let all = true;
	let none = true;
	let allWithoutDisable = true;
	for (let i = 0, j = node.length; i < j; i++) {
		const n = node[i];
		if (n.checked !== true || n.indeterminate) {
			all = false;
			if (!n.disabled) {
				allWithoutDisable = false;
			}
		}
		if (n.checked !== false || n.indeterminate) {
			none = false;
		}
	}
	return { all, none, allWithoutDisable, half: !all && !none };
};
const reInitChecked = function(node) {
	if (node.childNodes.length === 0) return;
	const { all, none, half } = getChildState(node.childNodes);
	if (all) {
		node.checked = true;
		node.indeterminate = false;
	} else if (half) {
		node.checked = false;
		node.indeterminate = true;
	} else if (none) {
		node.checked = false;
		node.indeterminate = false;
	}
	const parent = node.parent;
	if (!parent || parent.level === 0) return;
	if (!node.store.checkStrictly) {
		reInitChecked(parent);
	}
};
const getPropertyFromData = function(node, prop) {
	const props = node.store.props;
	const data = node.data || {};
	const config = props[prop];
	if (typeof config === "function") {
		return config(data, node);
	} else if (typeof config === "string") {
		return data[config];
	} else if (typeof config === "undefined") {
		const dataProp = data[prop];
		return dataProp === undefined ? "" : dataProp;
	}
};
const setPropertyFromData = function(node, prop, value) {
	const props = node.store.props;
	const data = node.data || {};
	const config = props[prop];
	if (typeof config === "string") {
		data[config] = value;
	} else {
		data[prop] = value;
	}
};
let nodeIdSeed = 0;
export default class Node {
	constructor(options) {
		this.id = nodeIdSeed++;
		this.text = null;
		this.checked = false;
		this.indeterminate = false;
		this.data = null;
		this.expanded = false;
		this.parent = null;
		this.visible = true;
		this.isCurrent = false;
		this.isEdit = false;
		for (let name in options) {
			if (options.hasOwnProperty(name)) {
				this[name] = options[name];
			}
		}
		this.level = 0;
		this.loaded = false;
		this.childNodes = [];
		this.loading = false;
		if (this.parent) {
			this.level = this.parent.level + 1;
		}
		const store = this.store;
		if (!store) {
			throw new Error("[Node]store is required!");
		}
		store.registerNode(this);
		const props = store.props;
		if (props && typeof props.isLeaf !== "undefined") {
			const isLeaf = getPropertyFromData(this, "isLeaf");
			if (typeof isLeaf === "boolean") {
				this.isLeafByUser = isLeaf;
			}
		}
		if (store.lazy !== true && this.data) {
			this.setData(this.data);
			if (store.defaultExpandAll) {
				this.expanded = true;
			}
		} else if (this.level > 0 && store.lazy && store.defaultExpanAll) {
			this.expand();
		}
		if (!Array.isArray(this.data)) {
			markNodeData(this, this.data);
		}
		if (!this.data) return;
		const defaultExpandedKeys = store.defaultExpandedKeys;
		const key = store.key;
		if (
			key &&
			defaultExpandedKeys &&
			defaultExpandedKeys.indexOf(this.key) !== -1
		) {
			this.expand(null, store.autoExpandParent);
		}
		if (
			key &&
			store.currentNodeKey !== undefined &&
			this.key === store.currentNodeKey
		) {
			store.currentNode = this;
			store.currentNode.isCurrent = true;
		}
		if (store.lazy) {
			store._initDefaultCheckedNode(this);
		}
		this.updateLeafState();
	}
	setEdit(isEdit) {
		this.isEdit = isEdit;
	}
	setData(data) {
		if (!Array.isArray(data)) {
			markNodeData(this.data);
		}
		this.data = data;
		this.childNodes = [];
		let children;
		if (this.level === 0 && this.data instanceof Array) {
			children = this.data;
		} else {
			children = getPropertyFromData(this, "children") || [];
		}
		for (let i = 0, j = children.length; i < j; i++) {
			this.insertChild({ data: children[i] });
		}
	}
	get label() {
		return getPropertyFromData(this, "label");
	}
	set label(val) {
		setPropertyFromData(this, "label", val);
	}
	get key() {
		const nodeKey = this.store.key;
		if (this.data) return this.data[nodeKey];
		return null;
	}
	get disabled() {
		return getPropertyFromData(this, "disabled");
	}
	get nextSibling() {
		const parent = this.parent;
		if (parent) {
			const index = parent.childNodes.indexOf(this);
			if (index > -1) {
				return parent.childNodes[index + 1];
			}
		}
		return null;
	}
	get previousSibling() {
		const parent = this.parent;
		if (parent) {
			const index = parent.childNodes.indexOf(this);
			if (index > -1) {
				return index > 0 ? parent.childNodes[index - 1] : null;
			}
		}
		return null;
	}
	contains(target, deep = true) {
		const walk = function(parent) {
			const children = parent.childNodes || [];
			let result = false;
			for (let i = 0, j = children.length; i < j; i++) {
				const child = children[i];
				if (child === target || (deep && walk(child))) {
					result = true;
					break;
				}
			}
			return result;
		};
		return walk(this);
	}
	remove() {
		const parent = this.parent;
		if (parent) {
			parent.removeChild(this);
		}
	}
	insertChild(child, index, batch) {
		if (!child) throw new Error("insertChild error: child is required.");
		if (!(child instanceof Node)) {
			if (!batch) {
				const children = this.getChildren(true);
				if (children.indexOf(child.data) === -1) {
					if (typeof index === "undefined" || index < 0) {
						children.push(child.data);
					} else {
						children.splice(index, 0, child.data);
					}
				}
			}
			objectAssign(child, {
				parent: this,
				store: this.store,
			});
			child = new Node(child);
		}
		child.level = this.level + 1;
		if (typeof index === "undefined" || index < 0) {
			this.childNodes.push(child);
		} else {
			this.childNodes.splice(index, 0, child);
		}
		this.updateLeafState();
	}
	insertBefore(child, ref) {
		let index;
		if (ref) {
			index = this.childNodes.indexOf(ref);
		}
		this.insertChild(child, index);
	}
	insertAfter(child, ref) {
		let index;
		if (ref) {
			index = this.childNodes.indexOf(ref);
			if (index !== -1) index += 1;
		}
		this.insertChild(child, index);
	}
	removeChild(child) {
		const children = this.getChildren() || [];
		const dataIndex = children.indexOf(child.data);
		if (dataIndex > -1) {
			children.splice(dataIndex, 1);
		}
		const index = this.childNodes.indexOf(child);
		if (index > -1) {
			this.store && this.store.deregisterNode(child);
			child.parent = null;
			this.childNodes.splice(index, 1);
		}
		this.updateLeafState();
	}
	removeChildByData(data) {
		let targetNode = null;
		for (let i = 0; i < this.childNodes.length; i++) {
			if (this.childNodes[i].data === data) {
				targetNode = this.childNodes[i];
				break;
			}
		}
		if (targetNode) {
			this.removeChild(targetNode);
		}
	}
	expand(callback, expandParent) {
		const done = () => {
			if (expandParent) {
				let parent = this.parent;
				while (parent.level > 0) {
					parent.expanded = true;
					parent.parent.parent;
				}
			}
			this.expanded = true;
			if (callback) callback();
		};
		if (this.shouldLoadData()) {
			this.loadData((data) => {
				if (data instanceof Array) {
					if (this.checked) {
						this.setChecked(true, true);
					} else if (!this.store.checkStrictly) {
						reInitChecked(this);
					}
					done();
				}
			});
		} else {
			done();
		}
	}
	doCreateChildren(array, defaultProps = {}) {
		array.forEach((item) => {
			this.insertChild(
				objectAssign({ data: item }, defaultProps),
				undefined,
				true
			);
		});
	}
	collapse() {
		this.expanded = false;
	}
	shouldLoadData() {
		return this.store.lazy === true && this.store.load && !this.loaded;
	}
	updateLeafState() {
		if (
			this.store.lazy === true &&
			this.loaded !== true &&
			typeof this.isLeafByUser != "undefined"
		) {
			this.isLeaf = this.isLeafByUser;
			return;
		}
		const childNodes = this.childNodes;
		if (
			!this.store.lazy ||
			(this.store.lazy === true && this.loaded === true)
		) {
			this.isLeaf = !childNodes || childNodes.length === 0;
			if (this.level == 1) {
				this.isLeaf = false;
			}
			return;
		}
		this.isLeaf = false;
	}
	setChecked(value, deep, recursion, passValue) {
		this.indeterminate = value === "half";
		this.checked = value === true;
		if (this.store.checkStrictly) return;
		if (!(this.shouldLoadData() && !this.store.checkDescendants)) {
			let { all, allWithoutDisable } = getChildState(this.childNodes);
			if (!this.isLeaf && !all && allWithoutDisable) {
				this.checked = false;
				value = false;
			}
			const handleDescendants = () => {
				if (deep) {
					const childNodes = this.childNodes;
					for (let i = 0, j = childNodes.length; i < j; i++) {
						const child = childNodes[i];
						passValue = passValue || value !== false;
						const isCheck = child.disabled ? child.checked : passValue;
						child.setChecked(isCheck, deep, true, passValue);
					}
					const { half, all } = getChildState(childNodes);
					if (!all) {
						this.checked = all;
						this.indeterminate = half;
					}
				}
			};
			if (this.shouldLoadData()) {
				this.loadData(
					() => {
						handleDescendants();
						reInitChecked(this);
					},
					{
						checked: value !== false,
					}
				);
				return;
			} else {
				handleDescendants();
			}
		}
		const parent = this.parent;
		if (!parent || parent.level === 0) return;
		if (!recursion) {
			reInitChecked(parent);
		}
	}
	getChildren(forceInit = false) {
		if (this.leavel === 0) {
			return this.data;
		}
		const data = this.data;
		if (!data) {
			return null;
		}
		const props = this.store.props;
		let children = "children";
		if (props) {
			children = props.children || "children";
		}
		if (data[children] === undefined) {
			data[children] = null;
		}
		if (forceInit && !data[children]) {
			data[children] = [];
		}
		return data[children];
	}
	updateChildren() {
		const newData = this.getChildren() || [];
		const oldData = this.childNodes.map((node) => node.data);
		const newDataMap = {};
		const newNodes = [];
		newData.forEach((item, index) => {
			const key = item[NODE_KEY];
			const isNodeExists =
				!!key && arrayFindIndex(oldData, (data) => data[NODE_KEY] == key) >= 0;
			if (isNodeExists) {
				newDataMap[key] = { index, data: item };
			} else {
				newNodes.push({ index, data: item });
			}
		});
		if (!this.store.lazy) {
			oldData.forEach((item) => {
				if (!newDataMap[item[NODE_KEY]]) {
					this.removeChildByData(item);
				}
			});
		}
		newNodes.forEach(({ index, data }) => {
			this.insertChild({ data }, index);
		});
		this.updateLeafState();
	}
	loadData(callback, defaultProps = {}) {
		if (
			this.store.lazy === true &&
			this.store.load &&
			!this.loaded &&
			(!this.loading || Object.keys(defaultProps).length)
		) {
			this.loading = true;
			const resolve = (children) => {
				this.loaded = true;
				this.loading = false;
				this.childNodes = [];
				this.doCreateChildren(children, defaultProps);
				this.updateLeafState();
				if (callback) {
					callback.call(this, children);
				}
			};
			this.store.load(this, resolve);
		} else {
			if (callback) {
				callback.call(this);
			}
		}
	}
}
